﻿using Anley.DomainDrivenDesign.Contract.Application;
using Anley.DomainDrivenDesign.Contract.DependencyInjection;
using Anley.DomainDrivenDesign.Contract.Enums;
using Anley.DomainDrivenDesign.Contract.Extension;
using Anley.DomainDrivenDesign.Contract.Helper;
using Anley.DomainDrivenDesign.Contract.Infrastructure;
using Anley.DomainDrivenDesign.Contract.Swagger;
using Anley.DomainDrivenDesign.Contract.Validate;

namespace Anley.DomainDrivenDesign.Contract;

/// <summary>
/// 主机扩展类
/// </summary>
public static class HostExtension
{
    /// <summary>
    /// 注册依赖注入
    /// </summary>
    /// <param name="services"></param>
    public static IServiceCollection RegisterDependencyInjection(
        this IServiceCollection services)
    {
        var entryAssembly = Assembly.GetEntryAssembly();
        var projectName = entryAssembly?.GetName().Name;
        if (string.IsNullOrWhiteSpace(projectName))
            throw new ArgumentNullException(nameof(projectName));
        var solutionName = projectName.Contains(".")
            ? projectName.Substring(0, projectName.IndexOf("."))
            : projectName;
        var searchPattern = $"{solutionName}.*.dll";
        var projectAssemblies = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, searchPattern)
            .Select(x => Assembly.Load(AssemblyName.GetAssemblyName(x)));
        var assemblies = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "Anley.*.dll")
            .Select(x => Assembly.Load(AssemblyName.GetAssemblyName(x)));
        assemblies = assemblies.Concat(projectAssemblies);
        return services.Scan(act =>
        {
            act.FromAssemblies(assemblies)
                .AddClasses(classes => classes.AssignableTo(typeof(IScopedAutoInject)))
                    .AsImplementedInterfaces()
                    .WithScopedLifetime()
                .AddClasses(classes => classes.AssignableTo(typeof(ISingletonAutoInject)))
                    .AsImplementedInterfaces()
                    .WithSingletonLifetime()
                .AddClasses(classes => classes.AssignableTo(typeof(ITransientAutoInject)))
                    .AsSelfWithInterfaces()
                    .WithTransientLifetime()
                .AddClasses(classes => classes.AssignableTo(typeof(ISelfScopedAutoInject)))
                    .AsSelf()
                    .WithScopedLifetime()
                .AddClasses(classes => classes.AssignableTo(typeof(ISelfSingletonAutoInject)))
                    .AsSelf()
                    .WithSingletonLifetime()
                .AddClasses(classes => classes.AssignableTo(typeof(ISelfTransientAutoInject)))
                    .AsSelf()
                    .WithTransientLifetime();
        });
    }

    /// <summary>
    /// 注册AutoMapper
    /// </summary>
    /// <param name="services"></param>
    public static IServiceCollection RegisterAutoMapper(
        this IServiceCollection services)
    {
        return services.AddAutoMapper(cfg =>
        {
            var baseType = typeof(MappingProfile);
            var mappingProfiles = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(t => t.GetTypes().Where(t => t.IsClass && t.IsAssignableTo(baseType) && !t.IsAbstract))
                .ToArray();
            foreach (var profile in mappingProfiles)
                cfg.AddProfile(profile);
        });
    }

    /// <summary>
    /// 配置模型状态
    /// </summary>
    /// <param name="services"></param>
    public static IServiceCollection ConfigureModelState(
        this IServiceCollection services)
    {
        return services.Configure<ApiBehaviorOptions>(opt =>
        {
            opt.InvalidModelStateResponseFactory = ctx =>
            {
                //获取验证失败的模型字段 
                var error = ctx.ModelState.FirstOrDefault();
                var innerError = error.Value?.Errors.FirstOrDefault();
                var message = innerError == null ? $"{error.Key}的值无效" : innerError.ErrorMessage;
                // 记录日志
                LogManager.GetCurrentClassLogger().Error("模型验证异常：" + message);
                // 统一返回格式
                return new JsonResult(AneResult.Fail(message));
            };
        });
    }

    /// <summary>
    /// 注册跨域
    /// </summary>
    /// <param name="services"></param>
    public static IServiceCollection RegisterCors(
        this IServiceCollection services)
    {
        return services.AddCors(opt =>
        {
            opt.AddPolicy(AneConsts.CorsName, ply =>
            {
                ply.AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials()
                    .SetIsOriginAllowed(_ => true);
            });
        });
    }

    /// <summary>
    /// 配置转发头
    /// </summary>
    /// <param name="services"></param>
    public static IServiceCollection ConfigureForwardedHeaders(
        this IServiceCollection services)
    {
        return services.Configure<ForwardedHeadersOptions>(opt =>
        {
            // 配置要处理的转发头部
            opt.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
            // 配置受信任的代理服务器 IP 地址或 IP 范围
            opt.KnownProxies.Add(IPAddress.Parse("127.0.0.1"));
            // 配置是否要使用逗号分隔的多个 IP 地址作为客户端 IP 地址
            opt.ForwardedForHeaderName = "X-Forwarded-For";
            // 配置代理服务器发送的原始协议头部字段
            opt.ForwardedProtoHeaderName = "X-Forwarded-Proto";
        });
    }

    /// <summary>
    /// 使用SqlServer数据库
    /// </summary>
    /// <param name="services"></param>
    /// <param name="connectionStringName">连接字符串名称</param>
    public static IServiceCollection UseSqlServer(
        this IServiceCollection services,
        string? connectionStringName = null)
    {
        if (!string.IsNullOrWhiteSpace(connectionStringName))
            SqlServerRepositoryContextProvider.SetConnectionStringName(connectionStringName);
        services.AddScoped<IDbRepositoryContextProvider, SqlServerRepositoryContextProvider>();
        return services;
    }

    /// <summary>
    /// 使用Redis缓存
    /// </summary>
    /// <param name="services"></param>
    /// <param name="connectionStringName"></param>
    /// <returns></returns>
    public static IServiceCollection UseRedis(
        this IServiceCollection services,
        string? connectionStringName = null)
    {
        if (!string.IsNullOrWhiteSpace(connectionStringName))
            RedisRepositoryContextProvider.SetConnectionStringName(connectionStringName);
        services.AddScoped<IRedisRepositoryContextProvider, RedisRepositoryContextProvider>();
        return services;
    }

    /// <summary>
    /// 注册Swagger
    /// </summary>
    /// <param name="title">标题</param>
    /// <param name="version">版本号</param>
    /// <param name="commentFileNames">注释文件名集合</param>
    public static IServiceCollection RegisterSwagger(
        this IServiceCollection services,
        string title,
        string version,
        params string[] commentFileNames)
    {
        return services.AddSwaggerGen(opt =>
        {
            opt.SwaggerDoc(version, new OpenApiInfo
            {
                Title = title,
                Version = version,
                Contact = new OpenApiContact()
                {
                    Name = AneConsts.AuthorName,
                    Email = AneConsts.AuthorEmail,
                    Extensions = new Dictionary<string, IOpenApiExtension>()
                    {
                        { "微信", new OpenApiString(AneConsts.WechatNo) },
                        { "QQ", new OpenApiString(AneConsts.QqNo) }
                    }
                }
            });
            if (commentFileNames != null && commentFileNames.Length > 0)
                foreach (var fileName in commentFileNames)
                    opt.IncludeXmlComments($"{AppDomain.CurrentDomain.BaseDirectory}{fileName}");
            opt.DocumentFilter<HiddenApiFilter>();
        })
            .AddSwaggerGenNewtonsoftSupport();
    }

    /// <summary>
    /// 注册本地化
    /// </summary>
    /// <param name="services"></param>
    /// <param name="defaultPathName">资源路径名称，默认为Resources</param>
    public static IServiceCollection RegisterLocalization(
        this IServiceCollection services,
        string? defaultPathName = null)
    {
        return services.AddLocalization(opt =>
        {
            opt.ResourcesPath = defaultPathName ?? AneConsts.DefaultLocalizerPathName;
        });
    }

    /// <summary>
    /// 注册HTTP客户端
    /// </summary>
    /// <param name="services"></param>
    public static IServiceCollection RegisterHttpClient(
        this IServiceCollection services)
    {
        return services
            .AddHttpClient();
    }

    /// <summary>
    /// 注册HTTP客户端
    /// </summary>
    /// <param name="services"></param>
    /// <param name="name"></param>
    /// <param name="baseUrl"></param>
    /// <param name="timeoutSeconds">超时等待时间（秒）</param>
    /// <param name="retryTimes">重试次数</param>
    /// <param name="retryWaitSeconds">重试等待时间</param>
    /// <returns></returns>
    public static IHttpClientBuilder RegisterHttpClient(
        this IServiceCollection services,
        string name,
        string baseUrl,
        int timeoutSeconds = 5,
        int retryTimes = 3,
        int retryWaitSeconds = 2)
    {
        var builder = services.AddHttpClient(
            name,
            client =>
            {
                client.BaseAddress = new Uri(baseUrl);
            })
            .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(timeoutSeconds)));
        if (retryTimes > 0)
            builder = builder
                .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(retryTimes, _ => TimeSpan.FromSeconds(retryWaitSeconds)));
        return builder;
    }

    /// <summary>
    /// 注册异常处理
    /// </summary>
    /// <param name="options"></param>
    public static void RegisterExceptionHandling(
        this MvcOptions options)
    {
        options.Filters.Add(typeof(GlobalExceptionFilter)); // 全局异常处理
    }

    /// <summary>
    /// 注册模型验证
    /// </summary>
    /// <param name="options"></param>
    /// <param name="resourceBaseName"></param>
    public static void RegisterModelValidation(
        this MvcOptions options,
        string assemblyString,
        string baseName)
    {
        var resourceManager = new ResourceManager(baseName, Assembly.Load(assemblyString));
        // 全局自定义验证消息
        options.ModelMetadataDetailsProviders.Add(new ApiValidationMetadataProvider(resourceManager));
        options.ModelMetadataDetailsProviders.Add(new ApiDisplayMetadataProvider(resourceManager));
    }

    /// <summary>
    /// 注册JSON
    /// </summary>
    /// <param name="builder"></param>
    public static IMvcBuilder RegisterNewtonsoftJson(
        this IMvcBuilder builder)
    {
        return builder.AddNewtonsoftJson(opt =>
        {
            opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();     // 驼峰式命名
            opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;                // 忽略循环引用
            opt.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;                        // 空值处理
            opt.SerializerSettings.Formatting = Formatting.Indented;                                    // 自动缩进
            opt.SerializerSettings.DateFormatString = AneConsts.DefaultDateTimeFormat;                  // 日期时间格式
        });
    }

    /// <summary>
    /// 注册NLog
    /// </summary>
    /// <param name="builder"></param>
    public static WebApplicationBuilder RegisterNLog(
        this WebApplicationBuilder builder)
    {
        builder.Logging.ClearProviders();
        builder.Logging.AddNLog("NLog.config");
        builder.Host.UseNLog();
        return builder;
    }

    /// <summary>
    /// 应用依赖注入
    /// </summary>
    /// <param name="app"></param>
    public static void ApplyDependencyInjection(
        this WebApplication app)
    {
        var scope = app.Services.CreateScope();
        ServiceProviderHelper.RegisterServiceProvider(scope.ServiceProvider);
    }

    /// <summary>
    /// 应用跨域
    /// </summary>
    /// <param name="builder"></param>
    public static IApplicationBuilder ApplyCors(
        this IApplicationBuilder app)
    {
        return app.UseCors(AneConsts.CorsName);
    }

    /// <summary>
    /// 应用转发头
    /// </summary>
    /// <param name="app"></param>
    public static IApplicationBuilder ApplyForwardedHeaders(
        this IApplicationBuilder app)
    {
        return app.UseForwardedHeaders();
    }

    /// <summary>
    /// 应用授权
    /// </summary>
    /// <param name="app"></param>
    public static IApplicationBuilder ApplyAuthorization(
        this IApplicationBuilder app)
    {
        app.UseMiddleware<AuthorizationMiddleware>();
        app.UseAuthentication();
        return app;
    }

    /// <summary>
    /// 应用本地化
    /// </summary>
    /// <param name="app"></param>
    /// <param name="defaultCultureName"></param>
    /// <param name="otherCultureNames"></param>
    public static IApplicationBuilder ApplyLocalization(
        this IApplicationBuilder app,
        string? defaultCultureName = null,
        params string[] otherCultureNames)
    {
        if (string.IsNullOrWhiteSpace(defaultCultureName))
            defaultCultureName = AneConsts.DefaultCultureName;
        var cultureNames = otherCultureNames.Unshift(defaultCultureName);
        return app.UseRequestLocalization(opt =>
        {
            opt.AddSupportedCultures(cultureNames);
            opt.AddSupportedUICultures(cultureNames);
            opt.SetDefaultCulture(defaultCultureName);
            opt.ApplyCurrentCultureToResponseHeaders = true;
            opt.AddInitialRequestCultureProvider(new AcceptLanguageHeaderRequestCultureProvider());
        });
    }

    /// <summary>
    /// 应用Swagger
    /// </summary>
    /// <param name="app"></param>
    /// <param name="title">标题</param>
    public static IApplicationBuilder ApplySwagger(
        this IApplicationBuilder app,
        string title,
        string version)
    {
        app.UseSwagger();
        app.UseSwaggerUI(stp =>
        {
            stp.SwaggerEndpoint($"/swagger/{version}/swagger.json", title);
        });
        return app;
    }

    /// <summary>
    /// 应用静态文件
    /// </summary>
    /// <param name="app"></param>
    public static IApplicationBuilder ApplyStaticFiles(
        this IApplicationBuilder app,
        params FileExtensions[] extensions)
    {
        var fileExtensionDic = new Dictionary<FileExtensions, string>
        {
            { FileExtensions.TXT, AneConsts.TXTContentType },
            { FileExtensions.XML, AneConsts.XMLContentType },
            { FileExtensions.JSON, AneConsts.JSONContentType },

            { FileExtensions.BMP, AneConsts.BMPContentType },
            { FileExtensions.PNG, AneConsts.PNGContentType },
            { FileExtensions.JPG, AneConsts.JPEGContentType },
            { FileExtensions.JPEG, AneConsts.JPEGContentType },

            { FileExtensions.DOC, AneConsts.DOCContentType },
            { FileExtensions.DOCX, AneConsts.DOCXContentType },
            { FileExtensions.XLS, AneConsts.XLSContentType },
            { FileExtensions.XLSX, AneConsts.XLSXContentType },
            { FileExtensions.PPT, AneConsts.PPTContentType },
            { FileExtensions.PPTX, AneConsts.PPTXContentType },
            { FileExtensions.CSV, AneConsts.CSVContentType },

            { FileExtensions.PDF, AneConsts.PDFContentType },
            { FileExtensions.OFD, AneConsts.OFDContentType },

            { FileExtensions.ZIP, AneConsts.ZIPContentType },
            { FileExtensions.RAR, AneConsts.RARContentType },

            { FileExtensions.MP3, AneConsts.MP3ContentType },
            { FileExtensions.MP4, AneConsts.MP4ContentType }
        };
        var allowExtensionDic = fileExtensionDic
            .Where(i => extensions.Contains(i.Key))
            .ToDictionary(i => i.Key.GetDescription(), i => i.Value);
        return app.UseStaticFiles(new StaticFileOptions()
        {
            ContentTypeProvider = new FileExtensionContentTypeProvider(allowExtensionDic)
        });
    }

    /// <summary>
    /// 应用路由
    /// </summary>
    /// <param name="endpoints"></param>
    public static void ApplyRoute(
        this IEndpointRouteBuilder endpoints)
    {
        endpoints.MapControllerRoute(
            name: "areas",
            pattern: "{area:exists}/{controller}/{action}/{id?}");
        endpoints.MapDefaultControllerRoute();
    }
}